package org.mv.mm.docs.nodes;

import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.mv.mm.api.Doc;
import org.mv.mm.api.Fragment;
import org.mv.mm.api.capabilities.IRefreshableFragmentViewCapability;
import org.mv.mm.api.capabilities.IRemovableFragmentCapability;
import org.mv.mm.api.data.IDataProvider;
import org.mv.mm.common.CentralLookup;
import org.mv.mm.docs.FragmentFlavor;
import org.mv.mm.docs.FragmentNewType;
import org.mv.mm.docs.actions.FragmentDetailAction;
import org.openide.actions.CopyAction;
import org.openide.actions.CutAction;
import org.openide.actions.MoveDownAction;
import org.openide.actions.MoveUpAction;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Utilities;
import org.openide.util.datatransfer.ExTransferable;
import org.openide.util.datatransfer.NewType;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;

/**
 *
 * @author Martin
 */
public final class FragmentNode extends AbstractNode {

  private static final Logger logger = Logger.getLogger(FragmentNode.class.getName());
  private IDataProvider dataProvider;
  private FragmentNewType fragmentType = null;
  public final static String TITLE_PROPERTY = "TITLE";
  private IRefreshableFragmentViewCapability rvc;

  /**
   * Constructor which calls the private method to create node for any fragment
   * (Fragment or Portion).
   *
   * @param lLeaf Boolean whether the created node should be leaf without any
   * childrens, or parent of some other children.
   * @param fragment
   * @param dataProvid
   */
  public FragmentNode(boolean lLeaf, Fragment fragment, Doc doc, IDataProvider dataProvid) {
    this(lLeaf, fragment, doc, dataProvid, new InstanceContent());
  }

  /**
   * Private constructor called by one of the public constructors, to prepare
   * parameters for calling parent (AbstractNode) constructor via super().
   *
   * @param lLeaf
   * @param fragment
   * @param dataProvider
   * @param ic
   */
  private FragmentNode(boolean lLeaf, final Fragment fragment, final Doc doc, final IDataProvider dataProvid, InstanceContent ic) {
    super((lLeaf) ? (Children.LEAF) : (Children.create(new FragmentNodeChildFactory(dataProvid, fragment, doc), true)), new AbstractLookup(ic));

    this.dataProvider = dataProvid;
    final String oldTitle;

    oldTitle = fragment.getWorkName();

    fragmentType = new FragmentNewType(dataProvid, this, doc, false);
    ic.add(fragmentType);

    ic.add(fragment);
    ic.add(dataProvid);

    CentralLookup centralLookup = CentralLookup.getDefault();

    /*
     * IRefreshableFragmentViewCapability
     */ rvc = new IRefreshableFragmentViewCapability() {

      @Override
      public void refreshChildren() throws Exception {
        List<Fragment> children = fragment.getChildrenFragments();
        if (children != null && children.size() > 0) {
          CentralLookup cl = CentralLookup.getDefault();
          for (Node node : FragmentNode.this.getChildren().getNodes()) {
            FragmentNode frn = (FragmentNode) node;
            cl.remove(frn.rvc);
          }

          setChildren(Children.create(new FragmentNodeChildFactory(FragmentNode.this.dataProvider, fragment, doc), true));
        } else {
          refreshAfterEdit(fragment);
        }
      }

      @Override
      public void refreshAfterEdit(Fragment frag) throws Exception {
        if (fragment == frag) {
          String newTitle;
          newTitle = fragment.getWorkName();
          fireDisplayNameChange(oldTitle, newTitle);
        }

      }

      @Override
      public void refreshAfterDelete(Fragment frag) throws Exception {
        //fireNodeDestroyed();
        //TODO něco s tím?
      }

      @Override
      public void refreshAfterNew(Fragment frag) throws Exception {
        //TODO něco s tím?
      }

      @Override
      public boolean canRefresh() {
        return false; // At least not yet, maybe later some functionality.
      }
    };
    ic.add(rvc);
    centralLookup.add(rvc);
  }

  @Override
  public String getHtmlDisplayName() {
    Fragment frag = getLookup().lookup(Fragment.class);
    if (frag.getTitle() == null || frag.getTitle().isEmpty()) {
      return "<i>" + frag.getWorkName() + "</i>";
    } else {
      return frag.getTitle();
    }
  }

  /**
   *
   * @param context
   * @return
   */
  @SuppressWarnings("unchecked")
  @Override
  public Action[] getActions(boolean context) {
    List<Action> fragmentActions = (List<Action>) Utilities.actionsForPath("Actions/AnyNode");
    fragmentActions.addAll(Utilities.actionsForPath("Actions/FragmentNode"));
    fragmentActions.add(CutAction.get(CutAction.class));
    fragmentActions.add(CopyAction.get(CopyAction.class));
    fragmentActions.add(MoveUpAction.get(MoveUpAction.class));
    fragmentActions.add(MoveDownAction.get(MoveDownAction.class));

    return fragmentActions.toArray(
            new Action[fragmentActions.size()]);
  }

  @Override
  public Action getPreferredAction() {
    return new Action() {//TODO vyřešit duplicitní kód tady a v FragmentDetailAction akci

      @Override
      public void actionPerformed(ActionEvent e) {
        FragmentDetailAction fdaction = new FragmentDetailAction(FragmentNode.this);
        fdaction.actionPerformed(e);
      }

      @Override
      public Object getValue(String key) {
        throw new UnsupportedOperationException("Not supported yet.");
      }

      @Override
      public void putValue(String key, Object value) {
        throw new UnsupportedOperationException("Not supported yet.");
      }

      @Override
      public void setEnabled(boolean b) {
        throw new UnsupportedOperationException("Not supported yet.");
      }

      @Override
      public boolean isEnabled() {
        return true;
      }

      @Override
      public void addPropertyChangeListener(PropertyChangeListener listener) {
        throw new UnsupportedOperationException("Not supported yet.");
      }

      @Override
      public void removePropertyChangeListener(PropertyChangeListener listener) {
        throw new UnsupportedOperationException("Not supported yet.");
      }
    };
  }

  @Override
  public boolean canCut() {
    return true;
  }

  @Override
  public boolean canCopy() {
    return true;
  }

  @Override
  public boolean canDestroy() {
    return true;
  }

  @Override
  public void destroy() throws IOException {
    Fragment fragment = getLookup().lookup(Fragment.class);
    IDataProvider dataProvid = getLookup().lookup(IDataProvider.class);
    IRemovableFragmentCapability rec =
            dataProvid.getLookup().lookup(IRemovableFragmentCapability.class);
    //TODO vyřešit větev pod. Jestli smazat, nebo jen nastavit o úroveň výš (asi spíš dotaz, a pak o úroveň)
    try {
      rec.remove(fragment);
    } catch (Exception e) {
      logger.log(Level.WARNING, null, e); //TODO provide a message from Bundle
    }

    //Notify the NodeListener in the RootNodeChildFactory,
    //where nodeDestroyed will call refresh on the ChildFactory:
    //fireNodeDestroyed(); //TODO nenahradí to stejně to následující s CentralLookup?

    CentralLookup cl = CentralLookup.getDefault();
    Collection<? extends IRefreshableFragmentViewCapability> nodes =
            cl.lookupAll(IRefreshableFragmentViewCapability.class);
    for (IRefreshableFragmentViewCapability rvcap : nodes) {
      try {
        rvcap.refreshAfterDelete(fragment);
      } catch (Exception ex) {
        logger.log(Level.WARNING, null, ex);
      }
    }

  }

  /**
   * Without this method, the NewAction does nothing on the node.
   *
   * @return FragmentNewType instance, which is called by action NewAction
   * (registered in layer.xml)
   */
  @Override
  public NewType[] getNewTypes() {
    return new NewType[]{fragmentType};
  }

  @Override
  public Transferable clipboardCut() throws IOException {
    Transferable deflt = super.clipboardCut();
    ExTransferable added = ExTransferable.create(deflt);
    added.put(new ExTransferable.Single(FragmentFlavor.CHAPTER_FLAVOR) {

      @Override
      protected Fragment getData() {
        return getLookup().lookup(Fragment.class);
      }
    });
    return added;
  }

  @Override
  public Transferable clipboardCopy() throws IOException {
    Transferable deflt = super.clipboardCopy();
    ExTransferable added = ExTransferable.create(deflt);
    added.put(new ExTransferable.Single(FragmentFlavor.CHAPTER_FLAVOR) {

      @Override
      protected Fragment getData() {
        return getLookup().lookup(Fragment.class);
      }
    });
    return added;
  }

  public IRefreshableFragmentViewCapability getRVC() {
    return this.rvc;
  }
}
